package state


// import "fmt"
import . "binchunk"
import "vm"
import ."api"
// import ."util"
import "compiler"


// 加载二进制chunk, 把主函数原型实例化为闭包并推入栈顶
// chunk: 字节数组, 给出要加载的chunk数据
// chunkName: 指定chunk的名字, 供加载错误或调试时使用
// mode: 加载模式, "b":必须是二进制chunk数据; "t":必须是文本chunk数据; "bt":可以是二进制或文本chunk数据
// 返回一个状态码, 0表示加载成功, 非0表示加载失败

// Lua函数全都是闭包，就连编译器为我们生成的主函数也是闭包
// 捕获了_ENV这个特殊的Upvalue，这个特殊Upvalue的初始化则是由API方法Load()来负责的
// 具体来说，Load()方法在加载闭包时，会看它是否需要Upvalue，如果需要，
// 那么第一个Upvalue(对于主函数来说就是_ENV)会被初始化成全局环境
// 其它Upvalue会被初始化成nil
func (self *luaState) Load(chunk []byte, chunkName, mode string) int {

	var proto *Prototype

	if IsBinaryChunk(chunk) {
		proto = Undump(chunk)
	} else {
		proto = compiler.Compile(string(chunk), chunkName)
	}
	//proto = compiler.Compile(string(chunk), chunkName)

	c := newLuaClosure(proto)
	self.stack.push(c)
	if len(proto.Upvalues) > 0 {  // 设置_ENV

		env := self.registry.get(LUA_RIDX_GLOBALS)
		
		c.upvals[0] = &upvalue{&env}

	}

	return LUA_OK
}


// 对Lua函数进行调用
// 在执行Call()方法之前必须先把被调函数推入栈顶, 然后把参数值依次推入栈顶
// Call()方法结束之后, 参数值和函数会被弹出栈顶, 取而代之的是指定数量的返回值
// nArgs: 指定准备传递给被调函数的参数数量, 同时也隐含给出了被调函数在栈里的位置
// nResults: 指定需要的返回值数量(多退少补), 如果是-1, 则被调函数的返回值会全部留在栈顶
func (self *luaState) Call(nArgs, nResults int) {

	val := self.stack.get(-(nArgs+1))

	c, ok := val.(*closure)

	if !ok {

		if mf := getMetafield(val, "__call", self); mf != nil {

			if c, ok = mf.(*closure); ok {

				self.stack.push(val)  // 以val作为第一个参数  // 作为参数的话, nArgs+1, 插入一个值会作为填充到时弹出
				self.Insert(-(nArgs + 2))

				nArgs += 1
			}
		}

	}

	if ok {

		if c.proto != nil {  // 如果是Lua闭包

			// fmt.Printf("call %s<%d, %d>\n", c.proto.Source, c.proto.LineDefined, 
			// c.proto.LastLineDefined)
			self.callLuaClosure(nArgs, nResults, c)

		} else {  // Go闭包
			self.callGoClosure(nArgs, nResults, c)

		}
	} else {
		panic("not function!")
	}
}


func (self *luaState) callLuaClosure(nArgs, nResults int, c *closure) {

	nRegs := int(c.proto.MaxStackSize)		// 执行函数所需的寄存器数量
	nParams := int(c.proto.NumParams)		// 定义函数时声明的固定参数数量
	isVararg := c.proto.IsVararg == 1		// 是否是可变参数

	newStack := newLuaStack(nRegs+LUA_MINSTACK, self) 		// 根据寄存器数量(适当扩大, 因为要给指令实现函数预留少量的栈空间)创建一个新的调用帧
	newStack.closure = c 					// 把闭包和调用帧联系起来

	funcAndArgs := self.stack.popN(nArgs + 1)		// 把函数和参数一次性从栈顶弹出
	newStack.pushN(funcAndArgs[1:], nParams)		// 按照固定参数数量传入参数
	newStack.top = nRegs			//固定参数传入完毕, 需要修改新的栈顶指针, 让它指向最后一个寄存器

	// 如果被调函数是vararg函数, 且传入参数的数量多于固定参数数量, 还需要把vararg参数记下来, 存在调用栈里, 以备后用
	if nArgs > nParams && isVararg {

		newStack.varargs = funcAndArgs[nParams+1:]
	}

	self.pushLuaStack(newStack)		// 把新调用帧推入调用栈顶, 让它成为当前帧
	self.runLuaClosure()	// 执行被调函数指令
	self.popLuaStack()		// 从调用栈顶弹出, 主调帧就又变成了当前帧

	// 被调函数运行完毕之后, 返回值会留在被调帧的栈顶(寄存器之上)
	// 需要把全部返回值从被调帧栈顶弹出, 然后根据期望的返回值数量多退少补, 推入当前帧栈顶
	// 函数调用至此才算结束
	if nResults != 0 {

		results := newStack.popN(newStack.top - nRegs)
		self.stack.check(len(results))
		self.stack.pushN(results, nResults)
	}
}


func (self *luaState) runLuaClosure() {

	for {

		inst := vm.Instruction(self.Fetch())
		// PrintInstruction(-1, inst)  // 用于调试
		inst.Execute(self)

		if inst.Opcode() == vm.OP_RETURN {

			break
		}
	}
}


func (self *luaState) callGoClosure(nArgs, nResults int, c *closure) {

	newStack := newLuaStack(nArgs + LUA_MINSTACK, self)  // 创建新的调用帧
	newStack.closure = c 	// 把闭包和调用帧联系起来

	args := self.stack.popN(nArgs)		// 把参数从主调帧里弹出
	newStack.pushN(args, nArgs)		// 把参数推入被调帧
	self.stack.pop()			// 从主调帧里弹出Go闭包

	self.pushLuaStack(newStack)		// 把新调用帧推入调用栈顶, 让它成为当前帧
	r := c.goFunc(self)			// 直接执行go函数
	self.popLuaStack()				// 从调用栈顶弹出, 主调帧就又变成了当前帧

	if nResults != 0 {

		results := newStack.popN(r)		// 从被调帧里弹出返回值
		self.stack.check(len(results))
		self.stack.pushN(results, nResults)	// 将返回值推入主调帧栈顶(多退少补)

	}

}


// 使用Go语言内置的panic()函数抛出错误
// 自然需要使用defer-recover机制来捕获异常
// 为了避免重复代码，把函数调用逻辑交给Call()方法去处理
// 如果一切正常，返回LUA_OK
// 否则，捕获并处理错误
func (self *luaState) PCall(nArgs, nResults, msgh int) (status int) {

	caller := self.stack

	status = LUA_ERRRUN

	// catch error

	// 调用Go语言内置的recover()函数从错误中恢复
	// 然后从调用栈依次弹出调用帧，知道到达发起调用的调用帧为止
	// 然后把错误对象推入栈顶，返回LUA_ERRRUN
	defer func() {
		
		if err := recover(); err != nil {

			for self.stack != caller {

				self.popLuaStack()

			}

			self.stack.push(err)

		}

	}()

	self.Call(nArgs, nResults)

	status = LUA_OK

	return

}